/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.base.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.iwindplus.base.domain.constant.CommonConstant;
import com.iwindplus.base.domain.constant.CommonConstant.FileConstant;
import com.iwindplus.base.domain.enums.BizCodeEnum;
import com.iwindplus.base.domain.exception.BizException;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * RestClient工具类.
 *
 * @author zengdegui
 * @since 2021/11/23
 */
@Slf4j
public class RestClientUtil {

    private RestClientUtil() {
        throw new IllegalStateException(CommonConstant.UTILITY_CLASS);
    }

    /**
     * get同步请求（参数拼接）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpGet(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.GET, url, header, null, param, clazz);
    }

    /**
     * get同步请求（参数拼接）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpGet(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.GET, url, header, null, param, typeRef);
    }

    /**
     * post同步请求（json）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param json       参数（必填）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPostByJson(RestClient httpClient, String url, Map<String, String> header, String json, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.POST, url, header, json, null, clazz);
    }

    /**
     * post同步请求（json）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param json       参数（必填）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPostByJson(RestClient httpClient, String url, Map<String, String> header, String json,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.POST, url, header, json, null, typeRef);
    }

    /**
     * post同步请求（FormBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPostByForm(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.POST, url, header, RestClientUtil.buildBody(param, null), null, clazz);
    }

    /**
     * post同步请求（FormBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPostByForm(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.POST, url, header, RestClientUtil.buildBody(param, null), null, typeRef);
    }

    /**
     * post同步请求（MultipartBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param files      文件集合（可选）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPostByMultipart(RestClient httpClient, String url, Map<String, String> header,
        Map<String, Object> param, List<MultipartFile> files, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.POST, url, header, RestClientUtil.buildBody(param, files), null, clazz);
    }

    /**
     * post同步请求（MultipartBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param files      文件集合（可选）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPostByMultipart(RestClient httpClient, String url, Map<String, String> header,
        Map<String, Object> param, List<MultipartFile> files, ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.POST, url, header, RestClientUtil.buildBody(param, files), null, typeRef);
    }

    /**
     * put同步请求（json）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param json       参数（必填）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPutByJson(RestClient httpClient, String url, Map<String, String> header, String json, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.PUT, url, header, json, null, clazz);
    }

    /**
     * put同步请求（json）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param json       参数（必填）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPutByJson(RestClient httpClient, String url, Map<String, String> header, String json,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.PUT, url, header, json, null, typeRef);
    }

    /**
     * put同步请求（FormBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPutByForm(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.PUT, url, header, RestClientUtil.buildBody(param, null), null, clazz);
    }

    /**
     * put同步请求（FormBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPutByForm(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.PUT, url, header, RestClientUtil.buildBody(param, null), null, typeRef);
    }

    /**
     * put同步请求（MultipartBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param files      文件集合（可选）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPutByMultipart(RestClient httpClient, String url, Map<String, String> header,
        Map<String, Object> param, List<MultipartFile> files, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.PUT, url, header, RestClientUtil.buildBody(param, files), null, clazz);
    }

    /**
     * put同步请求（MultipartBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param files      文件集合（可选）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpPutByMultipart(RestClient httpClient, String url, Map<String, String> header,
        Map<String, Object> param, List<MultipartFile> files, ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.PUT, url, header, RestClientUtil.buildBody(param, files), null, typeRef);
    }

    /**
     * delete同步请求（参数拼接）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpDelete(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.DELETE, url, header, null, param, clazz);
    }

    /**
     * delete同步请求（参数拼接）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpDelete(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.DELETE, url, header, null, param, typeRef);
    }

    /**
     * delete同步请求（json）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param json       参数（必填）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpDeleteByJson(RestClient httpClient, String url, Map<String, String> header, String json, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.DELETE, url, header, json, null, clazz);
    }

    /**
     * delete同步请求（json）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param json       参数（必填）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpDeleteByJson(RestClient httpClient, String url, Map<String, String> header, String json,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.DELETE, url, header, json, null, typeRef);
    }

    /**
     * delete同步请求（FormBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpDeleteByForm(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param, Class<T> clazz) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.DELETE, url, header, RestClientUtil.buildBody(param, null), null, clazz);
    }

    /**
     * delete同步请求（FormBody表单）.
     *
     * @param httpClient http客户端（必填）
     * @param url        路径（必填）
     * @param header     请求头（可选）
     * @param param      参数（可选）
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T httpDeleteByForm(RestClient httpClient, String url, Map<String, String> header, Map<String, Object> param,
        ParameterizedTypeReference<T> typeRef) {
        return RestClientUtil.executeRequest(httpClient, HttpMethod.DELETE, url, header, RestClientUtil.buildBody(param, null), null, typeRef);
    }

    /**
     * 执行同步请求，同步响应结果.
     *
     * @param httpClient http客户端（必填）
     * @param method     请求方式
     * @param url        路径
     * @param header     请求头
     * @param body       请求体（null，String(json) ，MultiValueMap）
     * @param uriParam   请求参数
     * @param clazz      返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T executeRequest(RestClient httpClient, HttpMethod method, String url, Map<String, String> header, Object body,
        Map<String, Object> uriParam, Class<T> clazz) {
        return doExecute(httpClient, method, url, header, body, uriParam, clazz, null);
    }

    /**
     * 执行同步请求，同步响应结果.
     *
     * @param httpClient http客户端（必填）
     * @param method     请求方式
     * @param url        路径
     * @param header     请求头
     * @param body       请求体（null，String(json) ，MultiValueMap）
     * @param uriParam   请求参数
     * @param typeRef    返回值类型（必填）
     * @param <T>        泛型
     * @return T
     */
    public static <T> T executeRequest(RestClient httpClient, HttpMethod method, String url, Map<String, String> header, Object body,
        Map<String, Object> uriParam, ParameterizedTypeReference<T> typeRef) {
        return doExecute(httpClient, method, url, header, body, uriParam, null, typeRef);
    }

    /**
     * 构建uri.
     *
     * @param url   路径
     * @param param 参数
     * @return URI
     */
    public static URI getUri(String url, MultiValueMap<String, String> param) {
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
        return builder.queryParams(param).build().encode().toUri();
    }

    /**
     * 构建参数.
     *
     * @param param 参数
     * @return MultiValueMap
     */
    public static MultiValueMap<String, String> buildMultiValueMap(Map<String, Object> param) {
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>(16);
        if (MapUtil.isNotEmpty(param)) {
            for (Map.Entry<String, Object> entry : param.entrySet()) {
                final Object value = entry.getValue();
                body.add(entry.getKey(), Objects.nonNull(value) ? value.toString() : null);
            }
        }
        return body;
    }

    /**
     * 构建body参数.
     *
     * @param param          参数
     * @param multipartFiles 文件集合
     * @return Object
     */
    public static MultiValueMap<String, Object> buildBody(Map<String, Object> param, List<MultipartFile> multipartFiles) {
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(16);
        if (MapUtil.isNotEmpty(param)) {
            return body;
        }
        for (Map.Entry<String, Object> entry : param.entrySet()) {
            body.add(entry.getKey(), entry.getValue());
        }

        if (CollUtil.isEmpty(multipartFiles)) {
            return body;
        }
        for (MultipartFile file : multipartFiles) {
            try {
                byte[] bytes = file.getBytes();
                ByteArrayResource resource = new ByteArrayResource(bytes, file.getOriginalFilename());
                body.add(FileConstant.FILE, resource);
            } catch (IOException e) {
                log.error("Skipping multipart file due to read error: {}, reason: {}", file.getOriginalFilename(), e.getMessage());
            }
        }
        return body;
    }

    private static <T> T doExecute(RestClient httpClient, HttpMethod method, String url, Map<String, String> header,
        Object body, Map<String, Object> uriParam, Class<T> clazz, ParameterizedTypeReference<T> typeRef) {

        boolean useClass = clazz != null;
        boolean useType = typeRef != null;
        if (useClass == useType) {
            throw new IllegalArgumentException("clazz and typeRef are mutually exclusive; exactly one must be specified.");
        }

        long start = System.nanoTime();
        try {
            URI uri = MapUtil.isEmpty(uriParam) ? URI.create(url)
                : getUri(url, buildMultiValueMap(uriParam));

            RestClient.RequestBodySpec spec = httpClient.method(method).uri(uri)
                .headers(h -> addHeaders(h, header, body));

            // 1. 仅在有 body 时才调用 .body()
            if (body != null) {
                spec.body(body);
            }

            // 2. 响应解析
            return useClass ? spec.retrieve().body(clazz)
                : spec.retrieve().body(typeRef);
        } catch (Exception ex) {
            log.error("{} {} -> ERROR: {} (cost={}ms)",
                method.name(), url, ex.getClass().getSimpleName(),
                TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start), ex);

            throw new BizException(BizCodeEnum.RPC_ERROR);
        }
    }

    private static void addHeaders(HttpHeaders h, Map<String, String> header, Object body) {
        if (MapUtil.isNotEmpty(header)) {
            header.forEach(h::add);
        }
        if (body instanceof String) {
            h.setContentType(MediaType.APPLICATION_JSON);
        } else if (body instanceof MultiValueMap<?, ?> mvm) {
            boolean multipart = mvm.values().stream().flatMap(List::stream).anyMatch(ByteArrayResource.class::isInstance);
            h.setContentType(multipart ? MediaType.MULTIPART_FORM_DATA : MediaType.APPLICATION_FORM_URLENCODED);
        }
    }

}
